package day0605;

import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Set;

/**
 * Selector（选择器）是Java NIO中能够检测一到多个NIO通道，并能够知晓通道是否为诸如读写事件做好准备的组件。
 * 这样，一个单独的线程可以管理多个channel，从而管理多个网络连接。
 * Created by Administrator on 2017/6/5.
 */
public class JSelectorDemo1 {
    public static void main(String[] args) throws Exception {
        /**
         * 建立选择器Selectors
         * 我们需要做的第一件事就是创建一个 Selector：
         * 然后，我们将对不同的通道对象调用 register() 方法，
         * 以便注册我们对这些对象中发生的 I/O 事件的兴趣。
         * register() 的第一个参数总是这个 Selector。
         */

        Selector selector = Selector.open();

        /**
         * 打开一个 ServerSocketChannel
         * 为了接收连接，我们需要一个 ServerSocketChannel。
         * 事实上，我们要监听的每一个端口都需要有一个 ServerSocketChannel 。
         * 对于每一个端口，我们打开一个 ServerSocketChannel，如下所示：
         *
         * 第一行创建一个新的 ServerSocketChannel ，最后三行将它绑定到给定的端口。
         * 第二行将 ServerSocketChannel 设置为 非阻塞的 。我们必须对每一个要使用的套接字通道调用这个方法，否则异步 I/O 就不能工作。
         */

        ServerSocketChannel ssc = ServerSocketChannel.open();
        ssc.configureBlocking(false); //非阻塞

//        ServerSocket ss = ssc.socket();
//        InetSocketAddress address = new InetSocketAddress(8866);
//        ss.bind(address);
        ssc.socket().bind(new InetSocketAddress(8866)); //同上三行

        /**
         * 选择键
         * 下一步是将新打开的 ServerSocketChannels 注册到 Selector上。
         * 为此我们使用 ServerSocketChannel.register() 方法，如下所示：
         * register() 的第一个参数总是这个 Selector。第二个参数是 OP_ACCEPT，这里它指定我们想要监听 accept 事件，也就是在新的连接建立时所发生的事件。这是适用于 ServerSocketChannel 的唯一事件类型。
         * 请注意对 register() 的调用的返回值。
         * SelectionKey 代表这个通道在此 Selector 上的这个注册。
         * 当某个 Selector 通知您某个传入事件时，它是通过提供对应于该事件的 SelectionKey 来进行的。
         * SelectionKey 还可以用于取消通道的注册。
         */

        SelectionKey key = ssc.register(selector, SelectionKey.OP_ACCEPT);

        /**
         * 内部循环
         * 现在已经注册了我们对一些 I/O 事件的兴趣，下面将进入主循环。
         * 使用 Selectors 的几乎每个程序都像下面这样使用内部循环：
         */

        //首先，我们调用 Selector 的 select() 方法。
        // 这个方法会阻塞，直到至少有一个已注册的事件发生。
        // 当一个或者更多的事件发生时， select() 方法将返回所发生的事件的数量。
        int num = selector.select();

        //接下来，我们调用 Selector 的 selectedKeys() 方法，它返回发生了事件的 SelectionKey 对象的一个 集合 。
        Set selectedKyes = selector.selectedKeys();
        Iterator it = selectedKyes.iterator();

        //我们通过迭代 SelectionKeys 并依次处理每个 SelectionKey 来处理事件。
        //对于每一个 SelectionKey，您必须确定发生的是什么 I/O 事件，以及这个事件影响哪些 I/O 对象。
        while (it.hasNext()) {
            SelectionKey keys = (SelectionKey)it.next();
        }

        /**
         * 监听新连接
         * 程序执行到这里，我们仅注册了 ServerSocketChannel，并且仅注册它们“接收”事件。
         * 为确认这一点，我们对 SelectionKey 调用 readyOps() 方法，并检查发生了什么类型的事件：
         */

        if ((key.readyOps() & SelectionKey.OP_ACCEPT) == SelectionKey.OP_ACCEPT ) {
            //可以肯定地说， readOps() 方法告诉我们该事件是新的连接。
        }

        /**
         * 接受新的连接
         * 因为我们知道这个服务器套接字上有一个传入连接在等待，所以可以安全地接受它；
         * 也就是说，不用担心 accept() 操作会阻塞：
         */

        ServerSocketChannel ssc2 = (ServerSocketChannel)key.channel();
        SocketChannel sc = ssc2.accept();

        //下一步是将新连接的 SocketChannel 配置为非阻塞的。
        // 而且由于接受这个连接的目的是为了读取来自套接字的数据，所以我们还必须将 SocketChannel 注册到 Selector上，如下所示：
        sc.configureBlocking(false);
        SelectionKey newKey = sc.register(selector, SelectionKey.OP_READ);
        //注意我们使用 register() 的 OP_READ 参数，将 SocketChannel 注册用于 读取 而不是 接受 新连接。

        /**
         * 删除处理过的 SelectionKey
         *
         * 在处理 SelectionKey 之后，我们几乎可以返回主循环了。
         * 但是我们必须首先将处理过的 SelectionKey 从选定的键集合中删除。
         * 如果我们没有删除处理过的键，那么它仍然会在主集合中以一个激活的键出现，这会导致我们尝试再次处理它。
         * 我们调用迭代器的 remove() 方法来删除处理过的 SelectionKey：
         */
        it.remove();
        //现在我们可以返回主循环并接受从一个套接字中传入的数据(或者一个传入的 I/O 事件)了。

        /**
         * 传入的 I/O
         * 当来自一个套接字的数据到达时，它会触发一个 I/O 事件。
         * 这会导致在主循环中调用 Selector.select()，并返回一个或者多个 I/O 事件。
         * 这一次， SelectionKey 将被标记为 OP_READ 事件，如下所示：
         */

        if ((key.readyOps() & SelectionKey.OP_READ) == SelectionKey.OP_READ) {
            // Read the data
            SocketChannel scs = (SocketChannel)key.channel();
            // ...
        }

        /**
         * 回到主循环
         * 每次返回主循环，我们都要调用 select 的 Selector()方法，
         * 并取得一组 SelectionKey。每个键代表一个 I/O 事件。
         * 我们处理事件，从选定的键集中删除 SelectionKey，然后返回主循环的顶部。
         * 这个程序有点过于简单，因为它的目的只是展示异步 I/O 所涉及的技术。
         * 在现实的应用程序中，您需要通过将通道从 Selector 中删除来处理关闭的通道。
         * 而且您可能要使用多个线程。这个程序可以仅使用一个线程，因为它只是一个演示，
         * 但是在现实场景中，创建一个线程池来负责 I/O 事件处理中的耗时部分会更有意义。
         */

    }
}
